﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace CodeLibrary.Extensions.Common
{
    [Flags]
    public enum TemporalGroupType
    {
        [TimeSpan("year", Days = 365)]
        year = 1,
        [TimeSpan("month", Days = 30, Hours = 12)]
        month = 2,
        [TimeSpan("week", Days = 7)]
        week = 4,
        [TimeSpan("day", Days = 1)]
        day = 8,
        [TimeSpan("hour", Hours = 1)]
        hour = 16,
        [TimeSpan("minute", Minutes = 1)]
        minute = 32,
        [TimeSpan("second", Seconds = 1)]
        second = 64
    }

    internal class TimeSpanAttribute : Attribute
    {
        public int Days
        {
            get;
            set;
        }

        public int Hours
        {
            get;
            set;
        }

        public int Minutes
        {
            get;
            set;
        }

        public int Seconds
        {
            get;
            set;
        }

        public string Name
        {
            get;
            private set;
        }

        public TimeSpanAttribute(string name)
        {
            this.Name = name;
        }

        public TimeSpan GetTimeSpan()
        {
            return new TimeSpan(
                this.Days, this.Hours,
                this.Minutes, this.Seconds);
        }

        public static TimeSpanAttribute RetrieveAttribute(Enum target)
        {
            object[] attributes =
                    target.GetType().GetField(target.ToString()).GetCustomAttributes(
                        typeof(TimeSpanAttribute), true);

            if (attributes != null && attributes.Length > 0)
                return (TimeSpanAttribute)attributes[0];
            else
                return null;
        }
    }

    internal static class TimeSpanArticulator
    {
        private const string Seperator = ",";
        private const string Plural = "s";
        private const string And = "and";
        private const string Space = " ";

        private const TemporalGroupType defaultAccuracy =
            TemporalGroupType.hour | TemporalGroupType.day |
            TemporalGroupType.week | TemporalGroupType.month |
            TemporalGroupType.year;

        internal class TemporalGrouping
        {
            internal TemporalGroupType Type
            {
                get;
                private set;
            }

            internal int Magnitude
            {
                get;
                private set;
            }

            internal TemporalGrouping(
                TemporalGroupType type, int magnitude)
            {
                this.Type = type;
                this.Magnitude = magnitude;
            }

            public override string ToString()
            {
                string result = this.Magnitude.ToString();

                result += " " + TimeSpanAttribute.RetrieveAttribute(this.Type).Name;

                if (this.Magnitude > 1)
                {
                    result += Plural;
                }

                return result;
            }
        }

        private static List<TemporalGroupType> groupTypes;

        static TimeSpanArticulator()
        {
            groupTypes = new List<TemporalGroupType>(Enum.GetValues(
                    typeof(TemporalGroupType)) as IEnumerable<TemporalGroupType>);
        }

        public static string Articulate(TimeSpan span)
        {
            return Articulate(span, defaultAccuracy);
        }

        public static string Articulate(TimeSpan span, TemporalGroupType accuracy)
        {
            List<TemporalGrouping> groupings =
                new List<TemporalGrouping>(4);

            foreach (TemporalGroupType type in groupTypes)
            {
                if ((accuracy & type) != type) continue;

                TimeSpan ts = TimeSpanAttribute.RetrieveAttribute(type).GetTimeSpan();

                if (span.Ticks >= ts.Ticks)
                {
                    int magnitude = (int)(span.Ticks / ts.Ticks);

                    groupings.Add(new TemporalGrouping(type, magnitude));

                    span = new TimeSpan(span.Ticks % ts.Ticks);
                }
            }

            return Textify(groupings);
        }

        private static string Textify(IList<TemporalGrouping> groupings)
        {
            string result = String.Empty;

            for (int i = 0; i < groupings.Count; i++)
            {
                string groupingStr = groupings[i].ToString();

                if (i > 0)
                {
                    if (i == groupings.Count - 1)
                    {
                        result += Space + And + Space;
                    }
                    else
                    {
                        result += Seperator + Space;
                    }
                }
                result += groupingStr;
            }

            return result;
        }
    }
}
